package Shared;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
import java.util.Set;

import org.jgrapht.Graphs;
import org.jgrapht.alg.DijkstraShortestPath;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.WeightedPseudograph;

/**
 * The Map module, see Architecture report for specifications.
 */

public class Map {
	private static Map singleton = new Map();

	public String pseudograph_bin_filename = "pseudograph.bin";

	private WeightedPseudograph<MapPosition, DefaultWeightedEdge> pseudograph;

	/**
	 * @return the unique Map instance.
	 */
	public static Map getInstance() {
		return singleton;
	}

	private Map() {
		loadPseudoGraph(); // to load the weighted pseudograph
	}

	/**
	 * Return a sequence of point representing the shortest path between a and b
	 * on the given map
	 * 
	 * @param a
	 *            the source position
	 * @param b
	 *            the destination position
	 * @return the list of MapPosition composing the shortest path between a and
	 *         b
	 */
	public List<MapPosition> computePath(MapPosition a, MapPosition b) {
		// XXX : avec cet algorithme, on ne tient pas compte des unreachable
		// roads
		// Solution ? Faire un dijkstra perso � la main qui ne passerait pas par
		// les nodes unreachable ?
		// ou alors faire une copie du graphe ou on enleverait tous les nodes
		// unreachable et ensuite appeler dijkstra?

		/*
		 * Normalement, aucun noead unreachable dans cette map, le graphe est
		 * connexe (cf. mail avec screen), c'est des routes carrossables :D
		 */
		/*
		 * Oui mais dans le mod�le de goal, on avait parl� d'un obstacle au cas
		 * o� une route ne serait pas accessible
		 */

		DijkstraShortestPath<MapPosition, DefaultWeightedEdge> research = new DijkstraShortestPath<MapPosition, DefaultWeightedEdge>(
				this.pseudograph, a, b);

		// On recupere un object GraphPath qu'on passe � la fonction
		// getPathVertexList pour avoir une liste des noeuds
		return Graphs.getPathVertexList(research.getPath());
	}

	/**
	 * Return the estimated "road to road" length of the shortest path between a
	 * and b on the given map
	 * 
	 * @param a
	 *            the source position
	 * @param b
	 *            the destination position
	 * 
	 * @return the shortest path length
	 */
	public double computeDistance(MapPosition a, MapPosition b) {

		double distance = 0.0;
		List<MapPosition> path = this.computePath(a, b);
		for (int i = 1; i < path.size(); i++) {
			distance += this.pseudograph.getEdgeWeight(this.pseudograph.getEdge(path.get(i - 1), path.get(i)));
		}

		return distance;
	}

	/**
	 * Obstacle : set a road in the map as "unreachable" if isBroken==true
	 * 
	 * @param a
	 *            first end of road
	 * @param b
	 *            seconde end of road
	 */
	public void setBrokenRoad(MapPosition a, MapPosition b) {

		DijkstraShortestPath<MapPosition, DefaultWeightedEdge> research = new DijkstraShortestPath<MapPosition, DefaultWeightedEdge>(
				this.pseudograph, a, b);

		List<MapPosition> list = Graphs.getPathVertexList(research.getPath());

		for (int i = 0; i < list.size(); i++) {
			MapPosition temp = (MapPosition) list.get(i);
			temp.setUnreachable();
		}

		this.pseudograph.setEdgeWeight(this.pseudograph.getEdge(a, b), Double.MAX_VALUE);
	}

	/**
	 * Returns a position randomly
	 * 
	 * @return a position
	 */
	public MapPosition getRandomPosition() {
		Set<MapPosition> tempSet = this.pseudograph.vertexSet();

		int size = tempSet.size();
		int i = 0;

		int randomNumber = (int) (Math.random() * (size)); // nombre aleatoire

		for (MapPosition temp : tempSet) {
			if (i == randomNumber)
				return temp;
			i++;
		}

		// arrive ici, aucune position n'est libre, on renvoie null.
		return null;
	}

	private void loadPseudoGraph() {
		Utils.disp("Loading " + pseudograph_bin_filename + " ...");

		ObjectInputStream in;
		try {

			in = new ObjectInputStream(new FileInputStream(new File(pseudograph_bin_filename)));
			pseudograph = (WeightedPseudograph<MapPosition, DefaultWeightedEdge>) in.readObject();
			Utils.disp("Loaded " + pseudograph_bin_filename);
		} catch (FileNotFoundException e) {
			Utils.disp("No pseudograph.bin found");
			System.exit(-1);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Returns the nearest map node of a position on the map.
	 * 
	 * @param pos
	 *            the position on the map
	 * @return the position of nearest node
	 */
	public MapPosition getNearestPos(MapPosition pos) {
		double best = Double.MAX_VALUE;
		MapPosition bestpos = null;
		for (MapPosition pos1 : this.pseudograph.vertexSet()) {
			double dist = pos1.getPoint().distance(pos.getPoint());
			if (dist < best) {
				bestpos = pos1;
				best = dist;
			}
		}
		return bestpos;
	}

	// public static void main(String[] args) {
	// Map map = new Map();
	// MapPosition node = map.getRandomPosition();
	// Utils.disp("" + map.computePath(node, node).size());
	// }

}
